package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"strconv"
	"sync"
	"time"
)

const (
	RETRY     = 3
	SLEEPTIME = time.Second * 1
)

type mysqlStore struct {
	mu sync.Mutex
	db *sql.DB
}

func NewMySQLStore() *mysqlStore {
	logger.Println("Build MySQL Store")
	return &mysqlStore{}
}

func (my *mysqlStore) Register(dbw DBWorker) error {

	err := my.connect()

	if err != nil {
		logger.SetPrefix("[ERROR]")
		logger.Fatalf("connnect MySQL failed, case %v", err)
		return err
	}

	logger.Println("Register MySQL Store to Handler")
	dbw.SetStore(my)

	return nil

}

func (my *mysqlStore) connect() error {
	my.mu.Lock()
	defer my.mu.Unlock()

	if my.db != nil {
		my.db.Close()
		my.db = nil
	}

	var err error
	my.db, err = sql.Open("mysql", dsn)
	if err != nil {
		return err
	}
	my.db.SetMaxOpenConns(maxOpenConns)
	my.db.SetMaxIdleConns(maxIdleConns)
	err = my.db.Ping()
	if err != nil {
		return err
	}

	return nil

}

func (my *mysqlStore) stop() error {
	my.mu.Lock()
	defer my.mu.Unlock()
	if my.db == nil {
		return nil
	}
	return my.db.Close()
}

func (my *mysqlStore) QueryResourcePath(uname string) (map[string]bool, error) {

	querySQL := `SELECT RESOURCE_PATH,RECURSIVE FROM POLICY WHERE UNAME_LIST LIKE '%` + uname + `%'`
	var err error
	var rows *sql.Rows
	for i := RETRY; i > 0; i-- {
		rows, err = my.db.Query(querySQL)
		if nil == err {
			break
		}
		time.Sleep(SLEEPTIME)
	}

	if err != nil {
		logger.Printf("%s case by %v", QUERY_PATH_FAILED, err)
		return nil, fmt.Errorf(QUERY_PATH_FAILED)
	}

	defer rows.Close()

	rs := make(map[string]bool)
	policyInfo := policyTB{}
	for rows.Next() {

		err = rows.Scan(&policyInfo.resource_path, &policyInfo.recursive)

		if err != nil {
			logger.Printf("%s case by %v", DECODE_PATH_FAILED, err)
			return nil, fmt.Errorf(DECODE_PATH_FAILED)
		}

		rs[policyInfo.resource_path.String], _ = strconv.ParseBool(strconv.FormatInt(policyInfo.recursive.Int64, 10))
	}

	return rs, nil

}

func (my *mysqlStore) UpdatePasswd(uname string, passwd string) error {

	execSQL := `UPDATE USER SET PASSWD = '` + passwd + `' WHERE UNAME = '` + uname + `'`
	var err error
	for i := RETRY; i > 0; i-- {
		_, err = my.db.Exec(execSQL)
		if nil == err {
			break
		}
		time.Sleep(SLEEPTIME)
	}

	if err != nil {
		logger.Printf("%s case by %v", UPDATE_PASSWD_FAILED, err)
		return fmt.Errorf(UPDATE_PASSWD_FAILED)
	}

	return nil
}

func (my *mysqlStore) QueryPasswd(uname string) (string, error) {

	querySQL := `SELECT PASSWD FROM USER WHERE UNAME = '` + uname + `'`
	var err error
	var rows *sql.Rows
	for i := RETRY; i > 0; i-- {
		rows, err = my.db.Query(querySQL)
		if nil == err {
			break
		}
		time.Sleep(SLEEPTIME)
	}

	if err != nil {
		logger.Printf("%s case by %v", QUERY_PASSWD_FAILED, err)
		return "", fmt.Errorf(QUERY_PASSWD_FAILED)
	}

	defer rows.Close()

	userInfo := userTB{}
	if rows.Next() {
		err = rows.Scan(&userInfo.passwd)
		if err != nil {
			logger.Printf("%s case by %v", DECODE_PASSWD_FAILED, err)
			return "", fmt.Errorf(DECODE_PASSWD_FAILED)
		}
	}

	return userInfo.passwd.String, nil

}

func (my *mysqlStore) SimpleQueryPasswd(uname string) (interface{}, error) {
	return my.QueryPasswd(uname)
}

func (my *mysqlStore) QueryToken(uname string) (*dynToken, error) {

	querySQL := `SELECT TOKEN,VALID_UNTIL FROM USER WHERE UNAME = '` + uname + `'`

	var err error
	var rows *sql.Rows
	for i := RETRY; i > 0; i-- {
		rows, err = my.db.Query(querySQL)
		if nil == err {
			break
		}
		time.Sleep(SLEEPTIME)
	}

	if err != nil {
		logger.Printf("%s case by %v", QUERY_TOKEN_FAILED, err)
		return nil, fmt.Errorf(QUERY_TOKEN_FAILED)
	}

	defer rows.Close()

	userInfo := userTB{}

	if rows.Next() {
		err = rows.Scan(&userInfo.token, &userInfo.valid_until)
		if err != nil {
			logger.Printf("%s case by %v", DECODE_TOKEN_FAILED, err)
			return nil, fmt.Errorf(DECODE_TOKEN_FAILED)
		}

	}

	return &dynToken{
		uname,
		userInfo.token.String,
		userInfo.valid_until.Int64,
	}, nil

}

func (my *mysqlStore) SimpleQueryToken(uname string) (interface{}, error) {
	return my.QueryToken(uname)
}

func (my *mysqlStore) UpdateToken(uname string, dyn *dynToken) error {

	execSQL := `UPDATE USER SET TOKEN = '` + dyn.token + `' , VALID_UNTIL = '` + strconv.FormatInt(dyn.valid_until, 10) + `' WHERE UNAME = '` + uname + `'`
	var err error
	for i := RETRY; i > 0; i-- {
		_, err := my.db.Exec(execSQL)
		if nil == err {
			break
		}
		time.Sleep(SLEEPTIME)
	}

	if err != nil {
		logger.Printf("%s case by %v", UPDATE_TOKEN_FAILED, err)
		return fmt.Errorf(UPDATE_TOKEN_FAILED)
	}

	return nil

}
